/**
 * 
 */
package com.jframework.module.usps;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;


import com.jframework.module.usps.Services.USPSService;
import com.jframework.module.usps.beans.ResponseError;
import com.jframework.module.usps.beans.USPSBaseResponseObject;
import com.jframework.module.usps.beans.rates.*;

/**
 * USPSShippingProcessor calculates USPS shipping rates. 
 * This class is main entry class for UI or standalong programs.
 * It directly sends XML API request to USPS server and XML response from it. 
 * From XML Response, it builds shipping options map, which get presented to callee program.
 * 
 * @author AJain
 *
 */
public class USPSShippingProcessor 
{

  static Log log = LogFactory.getLog(USPSShippingProcessor.class);
  
  private Map settings 						= null;
/*
 * USPS Variables, which get populated from properties file
 */
  protected String URL 						= null;
  protected String USERID 					= null; //<API_NAME @USERID=.. @PASSWORD=..>
  protected String SHIPPER_ZIP 				= null; //<ZipOrigination>...</ZipOrigination>
  protected Boolean machinable 				= new Boolean(false);    
  protected String default_Weight_Pounds 	= null;
  protected String default_Weight_Ounces 	= null;
  protected String package_Size 			= null;  
  protected boolean USPS_PackageUseDimensions ;
  protected String default_Width 			= null;  
  protected String default_Length 			= null;  
  protected String default_Height 			= null;
  private String USPS_Destination			= null;
  private String uspsDomesticServices 		= null;
  private String uspsInternationalServices	= null;
  private boolean domesticService;
  private boolean internationalService;
  private boolean addInsuranceInternational;
  private String default_Girth				= null;  

private String default_Insurance;

private double default_Insurance_double = 0.0;

private String uspsURL;

/*
 * USPS SHIPPING METHODS
 */
 
  public Collection getShippingOptions(Map requestParams) 
  throws Exception 
  {
    if (log.isDebugEnabled())
      log.debug("Loading USPS shipping options from properties file.");
    
    initialize();

//--------------------------------------------------------------------------//
    USPSBaseResponseObject methodsAndRates = getResponseFromUSPS(requestParams);
//  --------------------------------------------------------------------------//
    
    if (null != methodsAndRates && null == methodsAndRates.getError())   
    {
      Collection ratesBeans = buildRatesFromResponse(methodsAndRates);     
      return ratesBeans;      
    }
    else
    {
	  log(methodsAndRates.getError());
	  String errorMessage = methodsAndRates.getError().getDescription();
	  String errorCode = methodsAndRates.getError().getNumber();
	  throw new Exception(errorCode + ": " + errorMessage);
    }
  }

protected void log(ResponseError error) 
{
    if (null != error)
    {
	  log.error("Found ResponseError object "+error);	 
    }
    
}

protected Collection buildRatesFromResponse(USPSBaseResponseObject methodsAndRates) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException 
{
    
    if (methodsAndRates instanceof RateV3Response)
    {
	  return buildRatesFromRateV3Response((RateV3Response)methodsAndRates);	 
    }
    else if (methodsAndRates instanceof IntlRateResponse)
    {
	  return buildRatesFromIntlRateResponse((IntlRateResponse)methodsAndRates);
    }	
    
    return null;
}

/**
 * @param methodsAndRates
 * @throws IllegalAccessException
 * @throws InvocationTargetException
 * @throws NoSuchMethodException
 */
protected Collection buildRatesFromRateV3Response(RateV3Response methodsAndRates)
{
    //Collection hashMapRates = new ArrayList();
    List rates = new ArrayList();    
    int numberOfPackages = methodsAndRates.getPackage().length;
    for (int i = 0; i < numberOfPackages; i++)
    {
        RateV3ResponsePackage package1 = methodsAndRates.getPackage()[i];
        String packageId	= package1.getID();
        int numberOfPostages = package1.getPostage().length;
        for (int j = 0; j < numberOfPostages; j++)
        {
    	    RateV3Postage postage = package1.getPostage()[j];    
    	    String code = postage.getCLASSID().toString();
    	    if (this.isInCommaSeparatedString(code, this.uspsDomesticServices))
    	    {
    	      String name = postage.getMailService();
    	      Double rate = postage.getRate(); 
    	      ShippingOptionsAndRates bean = this.buildShippingBean(packageId, code, name, rate);
    	      rates.add(bean);                 
    	    }
        }
    }
    
    return rates;
}


protected ShippingOptionsAndRates buildShippingBean(String packageId,
		String code, String name, Double rate) {

	ShippingOptionsAndRates bean = new ShippingOptionsAndRates();
	bean.setPackageId(packageId);
	bean.setShippingCode(code);
	bean.setShippingOptionName(name);
	bean.setShippingRate(rate);
	return bean;
}

/**
 * @param methodsAndRates
 * @throws IllegalAccessException
 * @throws InvocationTargetException
 * @throws NoSuchMethodException
 */
protected Collection buildRatesFromIntlRateResponse(IntlRateResponse methodsAndRates) 
{
  
  if (null != methodsAndRates.getPackage())
  {
    List rates = new ArrayList(); 
  
    int numberOfPackages = methodsAndRates.getPackage().length;
    for (int i = 0; i < numberOfPackages; i++)
    {
	  IntlRateResponsePackage package1 = methodsAndRates.getPackage()[i];
	  String packageId = package1.getID();
	
	  if (null != package1.getService())
	  {
          int numberOfPostages = package1.getService().length;
          for (int j = 0; j < numberOfPostages; j++)
          {
            IntlRateResponsePackageService service = package1.getService()[j];    
    	    String code = service.getID();
    	    if (this.isInCommaSeparatedString(code, this.uspsInternationalServices))
    	    {
    	      String name = service.getSvcDescription();
    	      Double rate = service.getPostage();   
    	      
    	      Double insuranceCost = service.getInsurance();
    	      if (null != insuranceCost && this.addInsuranceInternational)
    	      {
    		    rate = new Double(rate.doubleValue()+insuranceCost.doubleValue());
    	      }
    	      
    	      ShippingOptionsAndRates bean = this.buildShippingBean(packageId, code, name, rate);
    	      rates.add(bean);                 
    	    }
          } //end of for loop
		} //end of null == error
      }    
      return rates;
  } //end of if null != packages
  return null;
}


  public void initialize() throws IOException 
  {    
	  
	  Properties properties = new Properties();	  
	  InputStream uspsPropertiesStream = 
		  this.getClass().getResourceAsStream("/accessusps.properties");	  
	  properties.load(uspsPropertiesStream);
	  
      this.SHIPPER_ZIP				=  properties.getProperty("uspsZIPOrigin");
      this.USERID					=  properties.getProperty("uspsUser");
      this.uspsURL					=  properties.getProperty("uspsURL");
      
      //Domestic, International
      this.USPS_Destination			= properties.getProperty("uspsDestination");
      this.domesticService			= this.isInCommaSeparatedString("01", this.USPS_Destination);
      this.internationalService		= this.isInCommaSeparatedString("02", this.USPS_Destination);      

//IDs of Priority Flat, express, parcel etc..      
      this.uspsDomesticServices		 	= properties.getProperty("uspsDomesticServices");
      this.uspsInternationalServices	= properties.getProperty("uspsIntlServices");
      
      this.machinable			= new Boolean(properties.getProperty("uspsMachinable"));
      this.package_Size			= properties.getProperty("uspsContainerSize");
      this.USPS_PackageUseDimensions = new Boolean(properties.getProperty("uspsPackageUseDimensions")).booleanValue();
      this.default_Length 	= properties.getProperty("uspsPackageDimensionsLength");
      this.default_Height 	= properties.getProperty("uspsPackageDimensionsHeight");
      this.default_Width 	= properties.getProperty("uspsPackageDimensionsWidth");
      this.default_Girth 	= properties.getProperty("uspsPackageDimensionsGirth");

      this.addInsuranceInternational 	= new Boolean(properties.getProperty("uspsPackageUseInsurance")).booleanValue();
      this.default_Insurance 			= properties.getProperty("uspsPackageMinimumInsuranceValue");
      
      if (null != this.default_Insurance)
        this.default_Insurance_double  	= new Double(this.default_Insurance).doubleValue();
    }


	public USPSBaseResponseObject getResponseFromUSPS(Map requestParams) throws Exception {
		if (log.isInfoEnabled())
			log.info("Loading active methods and rates from USPS.");

		USPSServiceFactory uspsServiceFactory = new USPSServiceFactory();
		USPSService service = null;
		USPSBaseResponseObject response = null;
		
		String country = (String)requestParams.get("destinationCountry");
		
		if (country!= null) 
		 if (country.equalsIgnoreCase("USA") &&  this.domesticService)
		 {//get and build domestic rates.
		    service = uspsServiceFactory.getUSPSService	(
		    		USPSConstants.API_TYPE_RateV3, 
		    		this.uspsURL, 
		    		"/RateV3Request.dtd");
		    RateV3Request rateV3Request = this.buildRateV3RequestObject(requestParams);
		    service.setRequestObject(rateV3Request);
		    response = service.doService(null);
		 }
		 else if (!country.equalsIgnoreCase("USA") && this.internationalService)
		 {//it is international shipping
		    service = uspsServiceFactory.getUSPSService(
		    		USPSConstants.API_TYPE_IntlRate, 
		    		this.uspsURL, 
		    		"/IntlRateRequest.dtd");
		    IntlRateRequest intlRateRequest = this.buildIntlRateRequestObject(requestParams);
		    service.setRequestObject(intlRateRequest);
		    response = service.doService(null);
		 }	
		
		return response;
	}
	
	

	protected IntlRateRequest buildIntlRateRequestObject(Map requestParams) 
	{
	    IntlRateRequest request = new IntlRateRequest();
	    
	    request.setUSERID(this.USERID);	    
		IntlRateRequestPackage package1 = new IntlRateRequestPackage();
		package1.setID("1");
		package1.setMailType("package");
		String country = (String)requestParams.get("destinationCountry");
		package1.setCountry(country);	

		String packageWeight	= (String)requestParams.get("packageWeight");
		Double weight = new Double(packageWeight);
		package1.setPounds(this.getWeightInPounds(weight));
		package1.setOunces(this.getWeightInOunces(weight));
		
		double declaredValue = this.default_Insurance_double;
		String declaredPackageValue = (String)requestParams.get("declaredValue");
		if (null != declaredPackageValue)
		 declaredValue = new Double(declaredPackageValue).doubleValue();
		
		if (this.addInsuranceInternational)
		  if ( declaredValue > this.default_Insurance_double )   
		     package1.setValueOfContents(new Double(declaredValue));
		  else
		     package1.setValueOfContents(new Double(this.default_Insurance_double));
		
		request.addPackage(package1);
	       
	    return request;	    
	}

	protected RateV3Request buildRateV3RequestObject(Map requestParams) 
	{
	    RateV3Request request = new RateV3Request();	    
	    request.setUSERID(this.USERID);	    
	    
		RateV3RequestPackage package1 = new RateV3RequestPackage();
		//package1.setID(""+i++);
		package1.setID("1");
		package1.setService(USPSConstants.MAIL_SERVICE_ALL);
		package1.setMachinable(this.machinable);
		package1.setSize(package_Size);
		package1.setZipOrigination(this.SHIPPER_ZIP);
		String zipDestination	= (String)requestParams.get("zipDestination");
		package1.setZipDestination(zipDestination);
			
		package1.setLength(new Double(this.default_Length));
		package1.setWidth(new Double(this.default_Width));
		package1.setHeight(new Double(this.default_Height));
		package1.setGirth(new Double(this.default_Girth));

		String packageWeight	= (String)requestParams.get("packageWeight");
		Double weight = new Double(packageWeight);
		package1.setPounds(this.getWeightInPounds(weight));
		package1.setOunces(this.getWeightInOunces(weight));
		request.addPackage(package1);
	    return request;
	}
	
	  
	private Integer getWeightInOunces(Double weight) 
	{
	    double ounces = Math.ceil((weight.doubleValue() - Math.floor(weight.doubleValue()))*100);
	    return new Integer(new Double(ounces*16/100).intValue());
	}

	private Integer getWeightInPounds(Double weight)
	{
	    return new Integer(weight.intValue());
	}

	protected boolean isInCommaSeparatedString(String value,
			String commaSeparatedString) {
	    
	    if (null == value || null == commaSeparatedString)
		return false;
	    
		boolean result = false;
		String[] separatedValues = commaSeparatedString.split(",");
		for (int j = 0; j < separatedValues.length; j++) {
			String thisValue = separatedValues[j].trim();
			if (value.equals(thisValue)) {
				result = true;
				break;
			}
		}
		return result;
	}

	public static void main(String[] args)
	{
	  try
	  {
		USPSShippingProcessor processor = new USPSShippingProcessor();
		Map requestParams = new HashMap();
		
		String destinationCountry = "India";
		String zipDestination	= "08520";
		String packageWeight	= "5.6";
		String declaredValue	= "250";
		
		requestParams.put("destinationCountry", destinationCountry);
		requestParams.put("zipDestination", 	zipDestination);
		requestParams.put("packageWeight",	 	packageWeight);
		requestParams.put("declaredValue", 		declaredValue);
		
		Collection shippingOptions = processor.getShippingOptions(requestParams);
		Iterator shippingIterator = shippingOptions.iterator();
		while(shippingIterator.hasNext())
		{
			ShippingOptionsAndRates bean = (ShippingOptionsAndRates)shippingIterator.next();
			System.out.println(bean);			
		}
		
	  }
	  catch(Exception ex)
	  {
		  ex.printStackTrace();
	  }
		
	}

}//end of class
